/*
* Flash-based Non-Volatile Memory (NVM)
* 
* This file supports storing and loading persistent configuration based on
* the STM32 builtin flash memory.
*
* The STM32F405xx has 12 flash sectors of heterogeneous size. We use the last
* two sectors for configuration data. These pages have a size of 128kB each.
* Setting any bit in these sectors to 0 is always possible, but setting them
* to 1 requires erasing the whole sector.
*
* We consider each sector as an array of 64-bit fields except the first N bytes, which we
* instead use as an allocation block. The allocation block is a compact bit-field (2 bit per entry)
* that keeps track of the state of each field (erased, invalid, valid).
*
* One sector is always considered the valid (read) sector and the other one is the
* target for the next write access: they can be considered to be ping-pong or double buffred.
*
* When writing a block of data, instead of always erasing the whole writable sector the
* new data is appended in the erased area. This presumably increases flash life span.
* The writable sector is only erased if there is not enough space for the new data.
*
* On startup, if there is exactly one sector
* whose last non-erased value has the state "valid" that sector is considered
* the valid sector. In any other case the selection is undefined.
*
*
* To write a new block of data atomically we first mark all associated fields
* as "invalid" (in the allocation table) then write the data and then mark the
* fields as "valid" (in the direction of increasing address).
*/
#include "bsp/bsp.h"
#include "os/types.h"
#include "utils/utils.h"
#include "board.h"
#include <string.h>


static uint32_t page_from_address(uint32_t Address)
{
	return (Address - CONFIG_FLASH_ADDRESS) / FLASH_PAGE_SIZE;
}

static void flash_erase_sector(uint32_t Sector)
{
	FLASH_EraseInitTypeDef EraseInitStruct;
	uint32_t PageError;
	EraseInitStruct.TypeErase   = FLASH_TYPEERASE_PAGES;
  	EraseInitStruct.Page        = Sector;
  	EraseInitStruct.NbPages     = 1;
	HAL_FLASHEx_Erase(&EraseInitStruct, &PageError);
}


// @brief Erases a flash sector. This sets all bits in the sector to 1.
// The sector's current index is reset to the minimum value (n_reserved).
// @returns 0 on success or a non-zero error code otherwise
int flash_erase_page(u32 address) {
	wdog_reload();
	HAL_FLASH_Unlock();
    flash_erase_sector(page_from_address(address));
	HAL_FLASH_Lock();
	wdog_reload();
    return 0;
}

int flash_erase_pages(u32 address, s32 size) {
	for (; size > 0; ) {
		flash_erase_page(address);
		address += FLASH_PAGE_SIZE;
		size -= FLASH_PAGE_SIZE;
	}
	return 0;
}

static void flash_write_dword(uint32_t Address, uint64_t Data)
{
	HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, Address, Data);
}

int flash_write_page(u32 target_addr, uint8_t *data, u32 length, bool erase) {
	int offset = 0;
	if (target_addr & 0x7) {
		return -1;
	}
    if (erase && flash_erase_page(target_addr) != 0) {
        return -1;
    }
	HAL_FLASH_Unlock();
    for (; length >= 8; data += 8, offset += 8, length -=8) {
        flash_write_dword(target_addr + offset, *(u64 *)data);
    }
	if (length > 0) {
		u64 last_dword = 0;
		memcpy(&last_dword, data, length);
		flash_write_dword(target_addr + offset, last_dword);
	}
	HAL_FLASH_Lock();
    return 0;
}

#if 0
void flash_write_magic(u32 magic) {
	u32 buffer[16];
	if (magic == REG32(CONFIG_IAP_INFO_ADDR + 8)) {
		return;
	}
	memcpy(buffer, (void *)CONFIG_IAP_INFO_ADDR, sizeof(buffer));

	buffer[2] = magic;

	flash_write_page(CONFIG_IAP_INFO_ADDR, (u8 *)buffer, sizeof(buffer), true);
}

u32 flash_read_magic(void) {
	return REG32(CONFIG_IAP_INFO_ADDR + 8);
}
#endif

